<template>
  <div class="msg-box">
    <div class="msg-header">
      <span class="header-title">{{ activeUser.toNickName }}</span>
    </div>
    <el-scrollbar ref="scrollbarRef" @scroll="handleScroll">
      <div class="more-message" v-if="!isMoreMessage">
        <span>暂无更多消息</span>
      </div>
      <div ref="msgListRef" :style="{ paddingTop: paddingTop + 'px' }">
        <div ref="msgViewListRef" class="message-list" :class="['message-gap']">
          <chat_message_item
            ref=""
            v-for="msg in viewList"
            :key="msg.id"
            :message="msg"
            :activeUser="activeUser"
            :messageImgList="messageImgList"
          />
        </div>
      </div>
    </el-scrollbar>

    <div class="right-bottom">
      <!-- <el-input
        input-style="background-color: #e5e4e400;box-shadow: none;padding: 15px 30px;line-height:24px;font-size:14px;color:#000;"
        :resize="'none'" v-model="chatContent" :rows="5" type="textarea" /> -->
      <div contenteditable="true" ref="chatContentRef" class="message-input"></div>
      <div class="chatOption">
        <el-button type="primary" @click="send()">发送</el-button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { sendSignalR } from "@/utils/websocket";
import { useUserStore } from "@/store";
import { computed } from "vue";
import { ElScrollbar } from "element-plus";
import { on, allDelete, emit } from "@/utils/mitt";
import { baseURL } from "@/api";
import ChatMessageApi from "@/api/chat/message";
import { ChatMessage, QueryChatMessage } from "@/api/chat/message/model";
import { ChatUserModel } from "@/api/chat/chatUser/model";
import { MessageTypeEnum, NodeType } from "@/enums/MessageTypeEnum";
import FileApi, { FileModel } from "@/api/file";
import { FileEnum } from "@/enums/FileEnum";
import {
  base64ToFile,
  blobToFile,
  getExtensionFromBase64,
  getExtensionFromMimeType,
} from "@/utils/fileUtils";
import { isUrl } from "@/utils/urlUtils";

const userStore = useUserStore();

const props = defineProps({
  // activeUser: Object as PropType<ChatUserModel>,
  activeUser: Object,
});

//监听新消息
watch(
  () => userStore.chatMessage.length,
  (newValue, oldValue) => {
    //新增的消息
    if (newValue > oldValue) {
      var newMsgList = userStore.chatMessage.filter((x) => x.pairID === props.activeUser.pairID);
      messageList.value.push(...newMsgList);
      newMsgList.forEach((ele) => {
        if (ele.messageType === MessageTypeEnum.IMG) {
          messageImgList.value.push(baseURL + ele.content);
        }
      });

      userStore.chatMessage = userStore.chatMessage.filter(
        (x) =>
          x.pairID != props.activeUser.pairID ||
          (x.pairID === props.activeUser.pairID && x.oneSelf === false)
      );

      autoMsgVirtualList();
    }
  }
);

const chatContentRef = ref();
const send = async () => {
  const {
    success,
    message,
    data: msgData,
  } = await ChatMessageApi.sendChatCheck(
    props.activeUser.touserId,
    chatContentRef.value.childNodes.length
  );
  if (!success) {
    ElNotification.warning(message);
    return;
  }
  chatContentRef.value.childNodes.forEach(async (element, index) => {
    const fileInfo = ref<FileModel>({
      mainId: msgData.msgIdList[index],
      module: FileEnum.CHAT_MESSAGE,
    });
    const chatMessage = ref<ChatMessage>({
      id: msgData.msgIdList[index],
      belongId: props.activeUser.id,
      touserId: props.activeUser.touserId,
      touserName: props.activeUser.toNickName,
      pairID: props.activeUser.pairID,
      content: "",
      timeStamp: msgData.timeStamp,
      oneSelf: true,
      isNewMessage: true,
      sendTime: msgData.sendTime,
      receiveTime: "",
      readTime: null,
      messageType: 1,
    });
    if (element.nodeType === NodeType.ELEMENT_NODE && element.nodeName === "IMG") {
      chatMessage.value.messageType = MessageTypeEnum.IMG;
      //元素节点
      if (isUrl(element.currentSrc)) {
        //获取文件
        const response = await fetch(element.currentSrc);
        if (response.ok) {
          const blob = await response.blob();
          fileInfo.value.file = blobToFile(
            blob,
            msgData.msgIdList[index] + getExtensionFromMimeType(blob.type)
          );
        }
      } else {
        fileInfo.value.file = base64ToFile(
          element.currentSrc,
          msgData.msgIdList[index] + getExtensionFromBase64(element.currentSrc)
        );
      }
      //上传文件
      const { success, message, data } = await FileApi.UploadSingleFile(fileInfo.value);
      if (success) {
        chatMessage.value.content = data.fileId;
        sendSignalR("OnMessage", chatMessage.value);
        chatMessage.value.content = data.fileUrl;
        userStore.chatMessage.push(chatMessage.value);
      } else {
        ElNotification.error(message);
      }
    } else {
      chatMessage.value.messageType = isUrl(element.textContent)
        ? MessageTypeEnum.LINK
        : MessageTypeEnum.TEXT;
      //文本节点
      chatMessage.value.content = element.textContent;
      sendSignalR("OnMessage", chatMessage.value);
      userStore.chatMessage.push(chatMessage.value);
    }
  });

  chatContentRef.value.innerHTML = "";

  autoMsgVirtualList();

  //更新所有未读消息
  const noReadList = messageList.value.filter((x) => x.isNewMessage === true);
  noReadList.forEach((e, i) => {
    updateMsgReaded(e);
  });
};

/**
 * 发送消息时自动滚动到底部
 */
const autoMsgVirtualList = () => {
  start.value = Math.max(messageList.value.length - itemCount, 0);
  end.value = messageList.value.length;

  computeScrollButtom();
};

/**
 * 更新消息为已读
 * @param msg
 */
const updateMsgReaded = async (msg: ChatMessage) => {
  if (msg.isNewMessage && msg.oneSelf === false) {
    const { success } = await ChatMessageApi.updateMsgReaded(msg.id);
    if (success) {
      //更新已读
      msg.isNewMessage = false;
      userStore.chatMessage = userStore.chatMessage.filter((x) => x.id != msg.id);

      emit("chat:readed:message", msg.pairID);
    }
  }
};

onMounted(async () => {
  //组件初始化时
  await queryMessage(props.activeUser);

  //初始化虚拟滚动列表
  createVirtualList();

  //监听切换聊天对象
  on("chat:active:user", async (data: ChatUserModel) => {
    await queryMessage(data);
    //初始化虚拟滚动列表
    createVirtualList();
  });
});

onUnmounted(() => {
  allDelete("chat:active:user");
});

/** 视图展示数据 */
const viewList = computed(() => {
  if (start.value === 0 && end.value === 0) {
    return [];
  }
  return messageList.value.slice(start.value, end.value);
});

const search = ref<QueryChatMessage>({});
/** 查询到的消息 */
const messageList = ref<ChatMessage[]>([]);
/** 聊天预览图片 */
const messageImgList = ref<string[]>([]);
const queryMessage = async (data: ChatUserModel) => {
  userStore.chatMessage = [];
  //查询对应好友消息
  search.value.belongId = data.id;
  search.value.timeStamp = null;
  const { dataList } = await ChatMessageApi.queryByUser(search.value);
  messageList.value = dataList;

  //预览图片
  dataList.forEach((element) => {
    if (element.messageType === MessageTypeEnum.IMG) {
      messageImgList.value.push(baseURL + element.content);
    }
  });
};

//创建虚拟列表
/** 前后buffer个数 */
let bufferCount = 20;
/** 消息间距 */
let marginBottom = 15;
/** 消息体高度*/
let itemHeight = 64 + marginBottom;
/** 消息间距，用于css设置gap属性 */
let gapMargin = marginBottom + "px";
/** 消息截取开始索引 */
let start = ref(0);
/** 消息截取结束索引 */
let end = ref(0);
/** 展示消息容器与总消息容器顶部的距离 */
let paddingTop = ref(0);
/** 滚动条高度 */
let scrollHeight = 0;
/** 滚动窗口高度 */
let viewHeight = 0;
/** 展示条数 */
let itemCount = 0;
/** 总消息容器，用于产生滚动条 */
let msgListRef = ref(null);
/** 展示消息容器，用于计算已经渲染数据的高度 */
let msgViewListRef = ref(null);
/** 可视窗口 */
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>();
/** 初始化虚拟滚动列表 */
const createVirtualList = () => {
  start.value = 0;
  end.value = 0;
  viewHeight = scrollbarRef.value.wrapRef.offsetHeight;
  scrollHeight = scrollbarRef.value.wrapRef.scrollHeight;
  itemCount = Math.ceil((viewHeight * 1.0) / itemHeight) + bufferCount;
  end.value = messageList.value.length;
  start.value = Math.max(end.value - itemCount, 0);

  computeScrollButtom();
};

const computeScrollButtom = () => {
  //dom更新后的操作
  nextTick(() => {
    //计算滚动条高度
    //1、先计算已经渲染数据的高度
    const msgViewHeight = msgViewListRef.value.offsetHeight;
    //2、计算未渲染数据高度
    let noViewHeight =
      (start.value - 0) * itemHeight + (messageList.value.length - end.value) * itemHeight;
    //计算滚动视口高度
    msgListRef.value.style.height = msgViewHeight + noViewHeight + "px";
    scrollHeight = scrollbarRef.value.wrapRef.scrollHeight;
    lastScrollTop = scrollHeight - viewHeight;
    paddingTop.value = start.value * itemHeight;
    scrollbarRef.value.setScrollTop(scrollHeight);
    autoScrollToButtom = true;
    isScrollBottom = true;
    isScrollTop = false;
    isMoreMessage.value = true;
  });
};

//滚动时更新虚拟列表
let autoScrollToButtom = false;
let lastScrollTop = 0;
let isScrollBottom = false;
let isScrollTop = false;
/** 是否还有数据 */
const isMoreMessage = ref(true);
/** 滚动时 */
const handleScroll = async (scrollInfo) => {
  const scrollTop = scrollInfo.scrollTop;
  if (autoScrollToButtom) {
    //初始化时自动滚动到底部
    autoScrollToButtom = false;
    return;
  }
  if (scrollTop === 0) {
    //滚到顶了
    isScrollTop = true;
    //TODO：加载更多数据
    if (isMoreMessage.value === true) {
      await queryMoreMessage();
    }
    return;
  } else {
    isScrollTop = false;
  }
  if (scrollTop + viewHeight >= scrollHeight) {
    //滚到底了
    isScrollBottom = true;
  } else {
    isScrollBottom = false;
  }
  //检测滚动方向
  if (scrollTop < lastScrollTop) {
    if (isScrollTop) {
      return;
    }
  } else {
    if (isScrollBottom) {
      return;
    }
  }
  // 更新最后的滚动位置
  lastScrollTop = scrollTop;

  computeVirtualList(scrollTop);
};

/** 计算数据索引 */
const computeVirtualList = (scrollTop) => {
  //计算索引
  itemCount = Math.ceil((viewHeight * 1.0) / itemHeight) + bufferCount;
  const startValue = Math.floor((scrollTop * 1.0) / itemHeight);
  start.value = Math.min(
    messageList.value.length - itemCount < 0 ? 0 : messageList.value.length - itemCount,
    startValue
  );
  end.value = Math.min(messageList.value.length, startValue + itemCount);
  computeScroll();
};

/** 更新滚动视图 */
const computeScroll = () => {
  //计算滚动条高度
  //1、先计算已经渲染数据的高度
  const msgViewHeight = msgViewListRef.value.offsetHeight;
  //2、计算未渲染数据高度
  let noViewHeight =
    (start.value - 0) * itemHeight + (messageList.value.length - end.value) * itemHeight;
  //更新滚动视口高度
  msgListRef.value.style.height = msgViewHeight + noViewHeight + "px";
  scrollHeight = scrollbarRef.value.wrapRef.scrollHeight;
  paddingTop.value = start.value * itemHeight;
};

/** 标识是否正在查询数据中 */
let queryMoreMessageIng = false;
const queryMoreMessage = async () => {
  queryMoreMessageIng = true;
  start.value = 0;
  end.value = Math.min(start.value + itemCount, messageList.value.length);
  search.value.belongId = messageList.value[start.value].belongId;
  search.value.timeStamp = messageList.value[start.value].timeStamp;
  console.log(search.value.timeStamp, start.value);
  const { dataList } = await ChatMessageApi.queryByUser(search.value);
  queryMoreMessageIng = false;
  if (dataList.length === 0) {
    isScrollTop = true;
    isMoreMessage.value = false;
  } else {
    isScrollTop = false;
    isMoreMessage.value = true;
    //重新渲染列表高度
    messageList.value = dataList.concat(...messageList.value);
    start.value = dataList.length;
    end.value = start.value + itemCount;

    //添加预览图片
    dataList.forEach((element) => {
      if (element.messageType === MessageTypeEnum.IMG) {
        messageImgList.value.push(baseURL + element.content);
      }
    });
    nextTick(() => {
      const msgViewHeight = msgViewListRef.value.offsetHeight;
      //2、计算未渲染数据高度
      let noViewHeight =
        (start.value - 0) * itemHeight + (messageList.value.length - end.value) * itemHeight;
      //计算滚动视口高度
      msgListRef.value.style.height = msgViewHeight + noViewHeight + "px";
      scrollHeight = scrollbarRef.value.wrapRef.scrollHeight;
      lastScrollTop = scrollHeight - viewHeight;
      paddingTop.value = start.value * itemHeight;
      scrollbarRef.value.setScrollTop(paddingTop.value);
    });
  }
};
</script>

<style>
.message-gap {
  gap: v-bind("gapMargin");
  padding-top: v-bind("gapMargin");
  padding-bottom: v-bind("gapMargin");
}
</style>

<style scoped lang="scss">
.msg-box {
  flex: 1;
  height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background-color: #e5e4e400;
}

.msg-header {
  display: flex;
  border-bottom: 1px solid #dadbdd;
}

.header-title {
  flex: 1;
  text-align: left;
  padding-left: 30px;
  font-size: 20px;
  height: var(--header-height);
  line-height: var(--header-height);
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.scroll-box {
  height: 100%;
  overflow: hidden;
}

.message-list {
  padding-left: 30px;
  padding-right: 30px;
  margin: 0;
  list-style: none;
  scroll-behavior: smooth;
  display: flex;
  flex-direction: column;
}

.more-message {
  text-align: center;
  color: #b3acac;
  font-size: 14px;
  margin-top: 15px;
}

.right-bottom {
  border-top: 1px solid #dadbdd;
  margin-bottom: 100px;

  .message-input {
    background-color: #e5e4e400;
    box-shadow: none;
    padding: 15px 30px;
    line-height: 24px;
    font-size: 14px;
    color: #000;
    min-height: 100px;
    max-height: 200px;
    overflow: auto;

    :deep(img) {
      max-height: 50px;
    }
  }

  .chatOption {
    text-align: right;
    margin-right: 30px;
  }
}
</style>
